/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2014-2019.
 * Description: driver for ddr ram kbox
 * Author: lichaoyue
 * Create: 2014-3-24
 */

#include <linux/mm.h>
#include <asm/io.h>
#include <linux/ioport.h>
#include "ddrramdriver.h"
#include "kbox_include.h"

#ifndef CONFIG_RTOS_KBOX_MINI
/* DDR Ram device name */
phys_addr_t ddr_ram_address;
phys_addr_t ddr_ram_size;
#endif
struct kbox_dev_des st_ddr_ram_device;

unsigned int kbox_default_reg_size = KBOX_DEFAULT_REG_SIZE;
bool kbox_compat_enable;
module_param(kbox_default_reg_size, uint, S_IRUSR | S_IWUSR | S_IRGRP);
module_param(kbox_compat_enable, bool, S_IRUSR | S_IWUSR | S_IRGRP);
unsigned int kbox_max_region_num = KBOX_SUPPORT_MAX_REG_NUM1;

static int kbox_parse_max_region_num(const char *str,
	const struct kernel_param *kp)
{
	int ret;
	unsigned int val;

	ret = kstrtouint(str, 0, &val);
	if (ret < 0) {
		error("parse max region num fail, str=%s\n", str);
		return -EINVAL;
	}

	if (kbox_dev_cb.dev_status == KBOX_DEV_STATUS_NOT_FORMAT) {
		if (val == KBOX_SUPPORT_MAX_REG_NUM1 ||
			val == KBOX_SUPPORT_MAX_REG_NUM2) {
			kbox_max_region_num = val;
			return 0;
		}
		error("invalid param, only 16 or 64 is valid\n");
	} else {
		notice("kbox memory had initialized\n");
	}

	return -EINVAL;
}
module_param_call(kbox_max_region_num, kbox_parse_max_region_num,
	param_get_uint, &kbox_max_region_num, S_IRUSR | S_IWUSR | S_IRGRP);

void kbox_kbox_dev_des_init(phys_addr_t addr, phys_addr_t size, char *mmap_addr)
{
	struct kbox_dev_des *cb = &st_ddr_ram_device;

	strncpy(cb->device_name, DDR_RAM_DEV_NAME, KBOX_DEV_NAME_MAXLEN);
	cb->module = THIS_MODULE;
	cb->map_op.phy = addr;
	cb->size = (u32)size;
	cb->map_op.mapped_addr = mmap_addr;
	cb->vm_flag = VM_IO;
}

int init_kbox_mem_by_ioremap(phys_addr_t addr, phys_addr_t size)
{
	int ret = 0;
	struct kbox_dev_des *cb = &st_ddr_ram_device;
	char *mmap_addr = NULL;

#ifndef CONFIG_PPC
	/*
	 * Powerpc don't support discontiguous System Ram.
	 * When reserved memory of kbox is in System Ram, it will cause
	 * following situations:
	 * 1. If the no-map attribute is added into reserved-memory node
	 *    in dts file, only first contiguous memory region of System
	 *    Ram will be used;
	 * 2. If the no-map attribute is not added into reserved-memory node
	 *    in dts file or if use /memreserve/ to reserve memory from System
	 *    Ram, the System Ram fails to register into the resource.
	 * To ensure kbox reliability in powerpc, disable request_mem_region
	 * before ioremap for ekbox in powerpc.
	 */
	if (!request_mem_region(addr, size, "kbox_ram")) {
		error("request mem region failed\n");
		return -EINVAL;
	}
#endif

	mmap_addr = ioremap_wc(addr, size);
	if (mmap_addr == NULL) {
		kbox_debug_trace();
		error("map kbox ddrram_address=%pa, ddrram_size=%llx fail\n",
			  &addr, (unsigned long long)size);
#ifndef CONFIG_PPC
		release_mem_region(addr, size);
#endif
		return -EADDRNOTAVAIL;
	}
	kbox_kbox_dev_des_init(addr, size, mmap_addr);
	ret = kbox_register_device(cb);
	if (ret < 0) {
		iounmap(mmap_addr);
		cb->map_op.mapped_addr = NULL;
		error("DDR Ram driver registration to kbox failed\n");
#ifndef CONFIG_PPC
		release_mem_region(addr, size);
#endif
	}
	return ret;
}

#ifndef CONFIG_RTOS_KBOX_MINI
static int kbox_parse_ddr_ram(const char *str, char **retptr)
{
	char *endp = NULL;
	phys_addr_t tmp;

	tmp = memparse(str, &endp);
	if (*endp == '@') {
		ddr_ram_size = tmp;
	} else {
		error("kbox_mem_setup fail, str=%s\n", str);
		return -EINVAL;
	}
	ddr_ram_address = memparse(endp + 1, retptr);
	return 0;
}

static int kbox_parse_def_size(char **str)
{
	int val;
	int ret;

	ret = get_option(str, &val);
	if (ret == 0) {
		error("parse kbox default size fail, str=%s\n", *str);
		return -EINVAL;
	}
	kbox_default_reg_size = (unsigned int)val;
	return ret;
}

static int __init kbox_mem_setup(char *str)
{
	char *retstr = NULL;
	int ret;

	ret = kbox_parse_ddr_ram(str, &retstr);
	if (ret < 0)
		goto out;

	if (*retstr != ',')
		return 0;

	retstr++;
	ret = kbox_parse_def_size(&retstr);

	/*
	 * The meaning of the return value is as follows:
	 * 1: No other content after def_region_size needs to continue
	 *    processing
	 * 2: There is a "," after def_region_size, and further analysis of
	 *    kbox_max_region_num is required.
	 * Other: parsing def_region_size error
	 */
	if (ret == 1)
		return 0;
	else if (ret == 2)
		ret = kbox_parse_max_region_num(retstr, NULL);
out:
	if (ret != 0) /* kbox_mem parse error, do not init kbox */
		ddr_ram_size = 0;
	return ret;
}
__setup("kbox_mem=", kbox_mem_setup);


/*
 * Description  : initialize the module and call the function ddr_ram_dev_desc_init
 * Input        : void
 * Output       : None
 * Called By    : This will be called by kernel when we insert the module in the kernel
 */
int __init ddr_ram_init(void)
{
	struct kbox_dev_des *cb = &st_ddr_ram_device;

	if (ddr_ram_size == 0) {
		notice("no find kbox mem in kernel cmdline, kbox disable...\n");
		return -EAGAIN;
	}
	memset(cb, 0, sizeof(*cb));

	return init_kbox_mem_by_ioremap(ddr_ram_address, ddr_ram_size);
}

/*
 * Description  : Uninitialize the module and call the function kbox_unregister_device
 * Input        : void
 * Output       : None
 * Return Value :void
 * Called By    : This will be called by kernel when we remove module from the kernel
 */
void ddr_ram_exit(void)
{
	char *ddr_mmap_addr = st_ddr_ram_device.map_op.mapped_addr;

	if (kbox_unregister_device(st_ddr_ram_device.device_name) < 0)
		error("DDR RAM driver Unregisteration from kbox failed\n");

	if (ddr_mmap_addr != NULL) {
#ifdef CONFIG_RTOS_KBOX_VIRTUAL
		if (use_vmalloc) {
			vfree(ddr_mmap_addr);
			return;
		}
#endif
		iounmap(ddr_mmap_addr);
#ifndef CONFIG_PPC
		release_mem_region(ddr_ram_address, ddr_ram_size);
#endif
	}
}
#endif /* end of ifndef CONFIG_RTOS_KBOX_MINI */

/*
 * Whether the size of the structure kbox_region_desc compatibility
 * with older versions(disable by default).
 */
static int __init setup_kbox_compat_enable(char *str)
{
#ifdef CONFIG_RTOS_KBOX_REGION_COMPAT
	kbox_compat_enable = true;
	kbox_compat_value = KBOX_COMPAT_ADJUST_SIZE;
#endif
	return 0;
}
__setup("kbox_compat_enable", setup_kbox_compat_enable);
